﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Xml;
using VersionComparer.Configuration;
using VersionComparer.Base;
using VersionComparer.Images;
using VersionComparer.DataModel.Xml;
using VersionComparer.Comparer;
namespace VersionComparer.DataModel
{
    public class XmlItem : ComparableItem 
    {
        protected static Image InitializeImage(Image image)
        {
            (image as Bitmap).MakeTransparent(Color.White);
            return image;
        }
        protected static Image emptyImage = InitializeImage( ImageResources.Empty);
        static XmlNode RenameAttribute(XmlAttribute node, string newName)
        {
            XmlDocument doc = node.OwnerDocument;
            XmlAttribute newNode = doc.CreateNode(node.NodeType, newName, null) as XmlAttribute;
            XmlNode parent = node.ParentNode;
            newNode.Value = node.Value;

            node.OwnerElement.Attributes.InsertAfter(newNode, node);
            node.OwnerElement.Attributes.Remove(node);

            return newNode;
        }
        static XmlNode RenameNode(XmlNode node, string newName)
        {
            XmlDocument doc = node.OwnerDocument;
            XmlNode newNode = doc.CreateNode(node.NodeType, newName, null);
            while (node.HasChildNodes)
            {
                newNode.AppendChild(node.FirstChild);
            }
            XmlAttributeCollection attributeCollection = node.Attributes;
            if (attributeCollection != null)
            {
                while (attributeCollection.Count > 0)
                    newNode.Attributes.Append(attributeCollection[0]);
            }
            XmlNode parent = node.ParentNode;

            parent.ReplaceChild(newNode, node);
            return newNode;
        }
       

        #region fields
        private string outerxml;
        XmlNode xmlNode;
        #endregion fields

        #region constructors

        public XmlItem()
        {
        }
        public XmlItem(ComparableItem parent , XmlNode node):base(parent)
        {
            XmlNode = node;

            if (node is XmlText || node is XmlComment)
                base.Text = node.Value;
            else
                base.Text = node == null ? "" : node.Name;
        }
        public XmlItem(ComparableItem parent, XmlNode node, string text):this ( parent, node)
        {
            base.Text = text;

        }
        #endregion constructors

        #region properties
        public override string FullText
        {
            get
            {
                if (outerxml == null)
                    outerxml = XmlNode == null ? "" : XmlNode.OuterXml;
                return outerxml;
            }
        }
        public XmlNode XmlNode
        {
            get
            {
                return xmlNode;
            }
            set
            {
                xmlNode = value;
                outerxml = null;
            }
        }
        public override ComparableItem RelatedItem
        {
            get
            {
                return base.RelatedItem;
            }
            set
            {
                if (RelatedItem != null && RelatedNode == null)
                    RelatedNode = (RelatedItem as XmlItem).XmlNode;
                base.RelatedItem = value;
            }
        }
        public XmlNode RelatedNode
        {
            get;
            private set;
        }
        protected override int HashCodeForMatch
        {
            get
            {
                return Text.GetHashCode();
            }
        }
        #endregion properties
        public override void ProcessFinished()
        {
            base.ProcessFinished();
            outerxml = null;
        }
       
        protected override Image GetImage()
        {
            return emptyImage;
        }
        XmlNode NodeParent(XmlNode node)
        {
            if (node == null)
                return null;
            if (node is XmlAttribute)
                return (node as XmlAttribute).OwnerElement;
            else
                return node.ParentNode;
        }
        protected XmlNode CopyNode(XmlItem source, XmlItem destination, bool deep)
        {
            XmlNode destinationNode = destination.XmlNode;
            XmlNode sourceNode = source.XmlNode;
            XmlNode destinationParent = NodeParent (destinationNode );
            XmlNode sourceParent = NodeParent (sourceNode );
            if (sourceNode == null)
            {
                if (destinationNode == null)
                    return null;
                else
                {

                    if (destinationParent == null)
                        destinationNode.OwnerDocument.RemoveAll ();
                    else
                        destinationParent.RemoveChild(destinationNode);
                    return null;
                }
            }
            if (destinationNode == null)
            {
                var parent = (destination.Parent as XmlItem );
                if ( parent == null )
                    return null;
                if (parent.XmlNode == null)
                    return CopyNode(source.Parent as XmlItem, destination.Parent as XmlItem, true);
                var parentXmlNode = parent.XmlNode;
                var nodeToInsert = parent.XmlNode.OwnerDocument.ImportNode(sourceNode, deep);
                for (int i = destination.Index - 1; i >= 0; i--)
                {
                    var sibling = destination.Sibling(i) as XmlItem ;
                    if (sibling.XmlNode != null && sibling.XmlNode.ParentNode != null)
                        return parentXmlNode.InsertAfter(nodeToInsert, sibling.XmlNode);
                }
                if (nodeToInsert is XmlAttribute)
                    return parentXmlNode.Attributes.Append(nodeToInsert as XmlAttribute);
                else
                {
                    return parentXmlNode.AppendChild(nodeToInsert);
                }
            }

            XmlNode newNode = destinationNode.OwnerDocument.ImportNode(sourceNode, deep);
            if (destinationNode is XmlText)
            {
                XmlText destinationText = destinationNode as XmlText;
                destinationText.Value = sourceNode.Value;
            }
            else
            if (destinationNode is XmlAttribute)
            {
                XmlAttribute destinationAttribute = destinationNode as XmlAttribute;
                destinationAttribute.Value = sourceParent.Attributes[destinationAttribute.Name].Value;
            }
            else
            {

                if (destinationParent == null)
                    destinationNode.OwnerDocument.LoadXml(destinationNode.OuterXml);
                else
                {
                   destinationParent.ReplaceChild(newNode, destinationNode);
                }
            }
            return newNode;
        }
        public override bool Copy (bool save)
        {
            if (save && !(Root as XmlFileItem).AskForSave(true))
                return false;
            CopyNode(this, RelatedItem as XmlItem, true);
            if ( save)
                RelatedItem.Save();
            return true;
        }
        public override bool ChangeValue(string newName)
        {
            
            if (!(Root as XmlFileItem).AskForSave(false))
                return false;
            if (XmlNode is XmlElement)
            {
                XmlNode = RenameNode(XmlNode, newName);
                return Save();
            }
            if (XmlNode is XmlAttribute)
            {
                XmlNode = RenameAttribute(XmlNode as XmlAttribute, newName);
                return Save();
            }
            if (XmlNode is XmlText)
            {
                XmlNode.Value = newName;
                return Save();
            }
            return false;
        }
        public override Difference DifferenceRelativeTo(ComparableItem item)
        {
            XmlItem otherItem = item as XmlItem;
            otherItem.RelatedNode = XmlNode;
            //if (XmlNode != null)
            //    Description = XmlNode.OuterXml;
            RelatedNode = otherItem != null ? otherItem.XmlNode : null;

            if (XmlNode == null)
            {
                if (RelatedNode != null)
                    Text = RelatedNode.Name;
                return Difference.Unexistant;
            }
            if (RelatedNode == null)
                return Difference.Unmatched ;
            

            if (XmlNode.Name != RelatedNode.Name)
            {
                if (string.Compare(XmlNode.Name, RelatedNode.Name, true) == 0)
                    return Difference.NameCase;
                else
                    return Difference.Name;
            }
            
            if (XmlNode.Value != RelatedNode.Value)
            {
                //if (!(XmlNode is XmlAttribute && RelatedNode is XmlAttribute))
                {
                    if (string.Compare(XmlNode.Value, RelatedNode.Value, true) == 0)
                    {
                        if (Settings.Current.IgnoreCase == CaseDifferences.Discard)
                            return Difference.None;
                        else
                            return Difference.ValueCase;
                    }
                    else
                        return Difference.Value;
                }

            }

            return Difference.None;

        }
        protected override int CompareToItem(ComparableItem other)
        {
            XmlItem otherXmlItem = (other as XmlItem);
            XmlNode otherNode = otherXmlItem.XmlNode;
            string otherName = otherNode == null ? "" : otherNode.Name;
            int result = XmlNode.Name.CompareTo(otherName);
            if (result != 0)
                return 2 * result;

            return FullText.CompareTo(otherXmlItem.FullText);
        }
        protected override ComparableItem[] CreateChilds()
        {
            List<ComparableItem> items = new List<ComparableItem>();
            if (XmlNode != null)
            {
                var attributes = XmlNode.Attributes;
                if (attributes != null)
                    foreach (XmlNode attribute in attributes)
                        items.Add(new XmlAttributeItem(this, attribute));

                var childNodes = XmlNode.ChildNodes;
                if (childNodes != null)
                {
                    foreach (XmlNode childNode in childNodes)
                    {
                        if (childNode is XmlComment)
                            items.Add(new XmlCommentItem(this, childNode));
                        else
                            if ( childNode is XmlText )
                                items.Add(new XmlTextItem(this, childNode));
                            else
                                if (childNode is XmlCDataSection)
                                        items.Add(new XmlCDataItem(this, childNode));
                                else
                                    items.Add(new XmlNodeItem(this, childNode));
                    }
                }
            }
            return items.ToArray();
        }
        public override double Matches(ComparisonAlgorithm algorithm, ComparableItem other)
        {
            if (Text != other.Text)
            {
                int tagsize1 = (Text.Length+2);
                int tagsize2 = (other.Text.Length + 2);

                string fullText1 = FullText;
                string fullText2 = other.FullText;

                if (fullText1.Length - 2 * tagsize1 != fullText2.Length- 2 * tagsize2)
                    return Match.NoMatch;
                if (fullText1.Substring(tagsize1, fullText1.Length - 2 * tagsize1) ==
                    fullText2.Substring(tagsize2, fullText2.Length - 2 * tagsize2))
                    return Match.NearCertainMatch;
                else
                    return Match.NoMatch;
            }

            return Match.MatchStrings(algorithm , FullText, other.FullText, true);
        }
       

    }
}
